﻿using System;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using GlobalStore.DataAccess.Mapping;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
using Configuration = NHibernate.Cfg.Configuration;
using IsolationLevel = System.Data.IsolationLevel;

namespace GlobalStore.DataAccess.Transactioning
{
    public abstract class NHibernateTransactionScope : ITransactionScope
    {
        //ToDo: Configure from app.config
        protected static readonly object syncRoot = new object();
        protected static ISessionFactory nHibernateSessionFactory;

        private static ISessionFactory CreateSessionFactory(bool recreateDatabase)
        {
            Action<MySQLConnectionStringBuilder> connectionStringExpression =
                x => x.Database("sql01_4161globals")
                      .Server("www.freesqldatabase.com")
                      .Username("sql01_41611")
                      .Password("globals");

            var fluentConfiguration = Fluently.Configure()
                .Database(MySQLConfiguration.Standard.ConnectionString(connectionStringExpression))

                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>());

            if (recreateDatabase)
                fluentConfiguration.ExposeConfiguration(BuildSchema);

            return fluentConfiguration
                .BuildConfiguration()
                //.SetProperty(NHibernate.Cfg.Environment.ReleaseConnections, "on_close") // ???
                .BuildSessionFactory();
        }

        protected ISessionFactory GetSessionFactory(bool recreateDatabase)
        {
            if (nHibernateSessionFactory == null)
            {
                lock (syncRoot)
                {
                    if (nHibernateSessionFactory == null)
                    {
                        nHibernateSessionFactory = CreateSessionFactory(recreateDatabase);
                    }
                }
            }
            return nHibernateSessionFactory;
        }

        private static void BuildSchema(Configuration config)
        {
            // this NHibernate tool takes a configuration (with mapping info in)
            // and exports a database schema from it
            new SchemaExport(config).Create(true, true);
        }

        protected bool isAlreadyDisposed;
        protected readonly ITransaction transactionScope;
        protected readonly ISession nHibernateSession;
        public ISession Session
        {
            get
            {
                if (nHibernateSession != null)
                    return nHibernateSession;
                // no null check, there should be a session
                return previousScope.Session;
            }
        }

        [ThreadStatic]
        protected static NHibernateTransactionScope currentScope;
        protected readonly NHibernateTransactionScope previousScope;
        public static NHibernateTransactionScope Current { get { return currentScope; } }

        public NHibernateTransactionScope(bool recreateDatabase, IsolationLevel isolationLevel, TimeSpan transactionTimeout, bool newSessionRequired)
        {
            //ToDO: Set TimeOut in Configuration

            isAlreadyDisposed = false;

            //Create session if no current scope exists or if explicitly requested)
            if (currentScope == null || newSessionRequired)
                nHibernateSession = GetSessionFactory(recreateDatabase).OpenSession();

            transactionScope = Session.BeginTransaction(isolationLevel);

            //Update current scope
            previousScope = currentScope;
            currentScope = this;
        }

        ~NHibernateTransactionScope()
        {
            Dispose(false);
        }

        public abstract void Complete();

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool isDisposing)
        {
            if (!isAlreadyDisposed)
            {
                isAlreadyDisposed = true;

                if (isDisposing)
                {
                    try
                    {
                        if (nHibernateSession != null)
                            nHibernateSession.Dispose();
                    }
                    catch (Exception ex)
                    {
                        //ServerLogger.ThrownExceptionsLog.Log(TraceEventType.Error, ex.ToString());
                        throw;
                    }
                    finally
                    {
                        currentScope = previousScope;
                        try
                        {
                            transactionScope.Dispose();
                        }
                        catch (Exception ex)
                        {
                            //ServerLogger.ThrownExceptionsLog.Log(TraceEventType.Error, ex.ToString());
                            throw;
                        }
                    }
                }

                GC.SuppressFinalize(this);
            }
        }
    }
}